package raytracer.objects;

import java.util.ArrayList;

import raytracer.math.*;
import raytracer.main.*;
import raytracer.misc.*;

public class BoundingBox {

	private Vector3d minValues;
	private Vector3d maxValues;
	private ThreeDObject boxObject;
	
	public BoundingBox(Scene s) {
		double minX=Double.MAX_VALUE;
		double minY=Double.MAX_VALUE;
		double minZ=Double.MAX_VALUE;
		double maxX=Double.MIN_VALUE;
		double maxY=Double.MIN_VALUE;
		double maxZ=Double.MIN_VALUE;
		for (ThreeDObject obj : s.getObjectList()) {
			if (!obj.isInfinite()) {
				minX=Math.min(obj.getMinX(), minX);
				minY=Math.min(obj.getMinY(), minY);
				minZ=Math.min(obj.getMinZ(), minZ);
				maxX=Math.max(obj.getMaxX(), maxX);
				maxY=Math.max(obj.getMaxY(), maxY);
				maxZ=Math.max(obj.getMaxZ(), maxZ);
			}			
		}
		this.minValues=new Vector3d(minX,minY,minZ);
		this.maxValues=new Vector3d(maxX,maxY,maxZ);
	}
	
	public BoundingBox(ArrayList<ThreeDObject> objectList){
		double minX=Double.MAX_VALUE;
		double minY=Double.MAX_VALUE;
		double minZ=Double.MAX_VALUE;
		double maxX=Double.MIN_VALUE;
		double maxY=Double.MIN_VALUE;
		double maxZ=Double.MIN_VALUE;
		for (ThreeDObject obj : objectList) {
			if (!obj.isInfinite()) {
				minX=Math.min(obj.getMinX(), minX);
				minY=Math.min(obj.getMinY(), minY);
				minZ=Math.min(obj.getMinZ(), minZ);
				maxX=Math.max(obj.getMaxX(), maxX);
				maxY=Math.max(obj.getMaxY(), maxY);
				maxZ=Math.max(obj.getMaxZ(), maxZ);
			}			
		}
		this.minValues=new Vector3d(minX,minY,minZ);
		this.maxValues=new Vector3d(maxX,maxY,maxZ);
	}
	
	public BoundingBox(ThreeDObject obj){
		double minX=Double.MAX_VALUE;
		double minY=Double.MAX_VALUE;
		double minZ=Double.MAX_VALUE;
		double maxX=Double.MIN_VALUE;
		double maxY=Double.MIN_VALUE;
		double maxZ = Double.MIN_VALUE;

		if (!obj.isInfinite()) {
			minX = Math.min(obj.getMinX(), minX);
			minY = Math.min(obj.getMinY(), minY);
			minZ = Math.min(obj.getMinZ(), minZ);
			maxX = Math.max(obj.getMaxX(), maxX);
			maxY = Math.max(obj.getMaxY(), maxY);
			maxZ = Math.max(obj.getMaxZ(), maxZ);
		}
		this.minValues = new Vector3d(minX, minY, minZ);
		this.maxValues=new Vector3d(maxX,maxY,maxZ);
	}
		
	public ThreeDObject getBoxObject() {
		return this.boxObject;
	}
	
	public Vector3d getMinValues() {
		return this.minValues;
	}
	
	public Vector3d getMaxValues() {
		return this.maxValues;
	}
	
	public double getMinX() {
		return minValues.x;
	}
	public double getMinY() {
		return minValues.y;
	}
	public double getMinZ() {
		return minValues.z;
	}
	
	public double getMaxX() {
		return maxValues.x;
	}
	public double getMaxY() {
		return maxValues.y;
	}
	public double getMaxZ() {
		return maxValues.z;
	}
	
	public String toString() {
		return new String("Min values: "+minValues+"\nMax values: "+maxValues);
	}

	/**
	 * mostly based on code by Marc Orthmann  
	 * @param ray
	 * @return
	 */
	public double[] intersect(Ray ray) {
		double t_far=Double.POSITIVE_INFINITY;
		double t_near=Double.NEGATIVE_INFINITY;
		Vector3d t1,t2;
		
		if ((ray.getDirection().x==0 && (ray.getOrigin().x<minValues.x||ray.getOrigin().x>maxValues.x))
		  ||(ray.getDirection().y==0 && (ray.getOrigin().y<minValues.y||ray.getOrigin().y>maxValues.y))
	      ||(ray.getDirection().z==0 && (ray.getOrigin().z<minValues.z||ray.getOrigin().z>maxValues.z))) {
			return null;
		}
		
		t1=new Vector3d(minValues);
		t2=new Vector3d(maxValues);
		Vector3d temp;
		t1.sub(ray.getOrigin());
		t2.sub(ray.getOrigin());
		t1.scale(1.0/ray.getDirection().x,1.0/ray.getDirection().y,1.0/ray.getDirection().z);
		t2.scale(1.0/ray.getDirection().x,1.0/ray.getDirection().y,1.0/ray.getDirection().z);
		
		if (t1.x>t2.x) {
			temp=t2;
			t2=t1;
			t1=temp;
		}
		if (t1.x>t_near) {
			t_near=t1.x;
		}
		if (t2.x<t_far) {
			t_far=t2.x;
		}		
		if (t_far<0||t_near>t_far) {
			return null;
		}
		
		if (t1.y>t2.y) {
			temp=t2;
			t2=t1;
			t1=temp;
		}
		if (t1.y>t_near) {
			t_near=t1.y;
		}
		if (t2.y<t_far) {
			t_far=t2.y;
		}		
		if (t_far<0||t_near>t_far) {
			return null;
		}
		
		if (t1.z>t2.z) {
			temp=t2;
			t2=t1;
			t1=temp;
		}
		if (t1.z>t_near) {
			t_near=t1.z;
		}
		if (t2.z<t_far) {
			t_far=t2.z;
		}		
		if (t_far<0||t_near>t_far) {
			return null;
		}
				
		return new double[]{t_near,t_far};		
	}
	
}
